package cn.ylz.ylmall.common.filter;

import cn.ylz.ylmall.account.dao.cache.IUserCacheRepository;
import cn.ylz.ylmall.account.pojo.po.UserLoginInfoPO;
import cn.ylz.ylmall.account.security.CurrentPrincipal;
import cn.ylz.ylmall.common.web.JsonResult;
import com.alibaba.fastjson.JSON;
import cn.ylz.ylmall.common.consts.HttpConsts;
import cn.ylz.ylmall.common.web.ServiceCode;
import io.jsonwebtoken.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;

@Component
@Slf4j
public class JwtAuthorizationFilter extends OncePerRequestFilter implements HttpConsts {
    @Value("${ylmall.jwt.secret-key}")
    private String secretKey;
    @Autowired
    private IUserCacheRepository userCacheRepository;
    public static final int JWT_MIN_LENGTH = 113;


    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        log.debug("处理JWT的过滤器开始处理当前请求……");
        String jwt = request.getHeader(HEADER_AUTHORIZATION);
        log.debug("客户端携带的JWT：{}", jwt);

        // 判断jwt是否有效
        if (!StringUtils.hasText(jwt) || jwt.length() < JWT_MIN_LENGTH) {
            // 对于无效的JWT，应该直接放行
            log.warn("当前请求中，客户端没有携带有效的JWT，将放行");
            filterChain.doFilter(request, response);
            return;
        }

        UserLoginInfoPO loginInfo = userCacheRepository.getLoginInfo(jwt);
        if (loginInfo == null) {
            // 放行，不会向SecurityContext中存入认证信息，则相当于没有携带JWT
            log.warn("在Redis中无此JWT对应的信息，将直接放行，交由后续的组件进行处理");
            filterChain.doFilter(request, response);
            return;
        }
        String ip = request.getRemoteAddr();
        String userAgent = request.getHeader(HEADER_USER_AGENT);
        if (!ip.equals(loginInfo.getIp()) && !userAgent.equals(loginInfo.getUserAgent())) {
            log.warn("本次请求的信息与当初登录时完全不同，将直接放行，交由后续的组件进行处理");
            filterChain.doFilter(request, response);
            return;
        }

        // 解析jwt
        log.debug("尝试解析JWT数据……");
        response.setContentType("application/json; charset=utf-8");
        Claims claims = null;
        try {
            claims = Jwts.parser()
                    .setSigningKey(secretKey)
                    .parseClaimsJws(jwt)
                    .getBody();
        } catch (ExpiredJwtException e) {
            log.warn("解析JWT时出现异常：ExpiredJwtException");
            String message = "操作失败，您的登录信息已经过期，请重新登录！";
            JsonResult jsonResult = JsonResult.fail(ServiceCode.ERR_JWT_EXPIRED, message);
            PrintWriter writer = response.getWriter();
            writer.println(JSON.toJSONString(jsonResult));
            writer.close();
            return;
        } catch (SignatureException e) {
            log.warn("解析JWT时出现异常：SignatureException");
            String message = "非法访问，你的本次操作已经被记录！";
            JsonResult jsonResult = JsonResult.fail(ServiceCode.ERR_JWT_SIGNATURE, message);
            PrintWriter writer = response.getWriter();
            writer.println(JSON.toJSONString(jsonResult));
            writer.close();
            return;
        } catch (MalformedJwtException e) {
            log.warn("解析JWT时出现异常：MalformedJwtException");
            String message = "非法访问，你的本次操作已经被记录！";
            JsonResult jsonResult = JsonResult.fail(ServiceCode.ERR_JWT_MALFORMED, message);
            PrintWriter writer = response.getWriter();
            writer.println(JSON.toJSONString(jsonResult));
            writer.close();
            return;
        } catch (Throwable e) {
            log.warn("解析JWT时出现异常：{}", e);
            String message = "检查服务器端的控制台";
            JsonResult jsonResult = JsonResult.fail(ServiceCode.ERROR_UNKNOWN, message);
            PrintWriter writer = response.getWriter();
            writer.println(JSON.toJSONString(jsonResult));
            writer.close();
            return;
        }

        Long id = claims.get("id", Long.class);
        String username = claims.get("username", String.class);

        // 凭证：无需凭证
        Object credentials = null;
        // 将解析得到的用户数据创建为Authentication对象
        CurrentPrincipal principal = new CurrentPrincipal();
        principal.setId(id);
        principal.setUsername(username);

        // 检查用户的启用状态
        Integer userEnable = userCacheRepository.getEnableByUserId(id);
        if (userEnable != 1) {
            log.warn("用户已被禁用");
            String message = "用户已被禁用";
            JsonResult jsonResult = JsonResult.fail(ServiceCode.ERROR_UNAUTHORIZED_DISABLED, message);
            PrintWriter writer = response.getWriter();
            writer.println(JSON.toJSONString(jsonResult));
            writer.close();
            return;
        }

        // 权限列表从Redis中读取
        String authoritiesJsonString = loginInfo.getAuthoritiesJsonString();
        List<SimpleGrantedAuthority> authorityList = JSON.parseArray(authoritiesJsonString, SimpleGrantedAuthority.class);
        Authentication authentication = new UsernamePasswordAuthenticationToken(principal, credentials, authorityList);

        // 将Authentication对象存入到SecurityContext中
        SecurityContext securityContext = SecurityContextHolder.getContext();
        securityContext.setAuthentication(authentication);

        // 过滤器链继续执行，即：放行
        filterChain.doFilter(request, response);

    }
}
